//: Playground - noun: a place where people can play

import UIKit

// 1.swift和OC协议的区别

/*
 1.OC 中的协议只能用于类,swift中的协议不仅能用于类,还能用于结构体和枚举
 2.OC 中的协议可以设置可选实现,但Swift中的协议必须实现
 3.Swift中的协议增加了一个关键字mutating可以决定结构体和枚举实现协议的时候,是否可以修改属性的值
 */

// 定义一个protocol

protocol PersonProtocol {

    func getName()
    func getSex()
}

// 某个class、strruct、enum要遵守这种约定的话，需要实现约定的方法

struct Person: PersonProtocol {

    func getName() {
        
        print("melodyzhy")
    }
    
    func getSex() {
         print("boy")
    }
}

// 1.protocol中的约定方法，当方法中有参数时是不能有默认值的
// 2.protocol中也可以定义属性，但必须明确指定该属性支持的操作：只读（get）或者是可读写（get set）

protocol PersonProtocol1 {
    
    // 我们也可以在protocol中定义属性
    // ⚠️必须明确指定该属性支持的操作：只读（get）或者是可读写（get set）
    var height: Int {get set }
    var weight: Int { get }
    func getName()
    func getSex()
    // protocol中的约定方法，当方法中有参数时是不能有默认值的
    // ❌ Default argument not permitted in a protocol method
    // func getAge(age: Int = 18)
    func getAge(age: Int)
}

struct Person1: PersonProtocol1 {

    var height: Int = 178
    var weight: Int = 120
    
    func getName() {
        print("MelodyZhy")
    }
    func getSex() {
        print("boy")
    }
    func getAge(age: Int) {
        print("age = \(age)")
    }
    
}

var person = Person1()
person.height   // 178
person.height = 180
person.height  // 180

// 可以更改（但在以前版本的Swift中是不能直接这样更改的
// 需要借助一个内部的stored property
// 然后把这个属性设计成一个computed property实现 get 和 set 方法）
person.weight
person.weight = 130
person.weight

// 当我们把person从Person转换成PersonProtocol时 他就是只读的了
// ❌Cannot assign to property: 'weight' is a get-only property
//(person as PersonProtocol1).weight = 120

// 定义可选的protocol属性或者方法
@objc protocol PersonProtocol2 {

    @objc optional var height: Int { get set }
    @objc optional var weight: Int { get }
    @objc optional func getName()
    @objc optional func getSex()
    @objc optional func getAge(age: Int)
}

class Person2: PersonProtocol2 {

    // 如果想提供可选的约定方法或者属性那么只能定义@objc的protocol
    // 并且这种约定只能class能遵守
}

// protocol可以继承，当然struct、class、enum都可以同时遵守多个约定

protocol Engineer: PersonProtocol {

    var good: Bool { get }
}

protocol Animal {

}

struct Person3: Engineer, Animal {
    internal func getSex() {
        
    }

    internal func getName() {
        
    }

    internal var good: Bool
}

// CustomStringConvertible

struct Point {

    var x:Int
    var y:Int
}

let p = Point(x: 10, y: 10)
print(p)

// 如果我们想让打印界面变成 x ＝ 10, y = 10
// 那么我们就要遵守 CustomStringConvertible 这个 protocol
// 来看下实现

extension Point: CustomStringConvertible {

    var description: String {
        
        return "x = \(self.x), y = \(self.y)"
    }
}

print(p)

// Equatable
extension Point: Equatable {}

func == (lP: Point, rP: Point) -> Bool {

    let equalX = lP.x == rP.x
    let equalY = lP.y == rP.y
    return equalX && equalY
}

let lP = Point(x: 10, y: 10)
let rP = Point(x: 10, y: 10)

// 不要以为是我们重载了 == 函数 就能自动推导出 !=
// 其实这都是 Equatable 这个 protocol 的功劳
// 当然我们如果要遵守 Equatable 这个 protocol 就必须实现 == 函 数 或者  != 函数
// 如果我们只是重载了 == 函数 是不能用 !=
if lP != rP {
    print("unequal")
} else {
    print("equal")
}

// Comparable
// 想遵守 Comparable 这个 protocol 必须遵守 Equatable
// 同时用必须实现 < 函数 或者 > 函数
extension Point: Comparable {}
// 同样 > 函数会自动推导出来
func < (lP: Point, rP: Point) -> Bool {
    // 随意定义的规则 不必在意
    let x = lP.x - rP.x
    let y = lP.y - rP.y
    return x < y
}
// 实现了 Comparable 我们就可以把 Point 放到 Array 中，同时支持使用各种排序方法


// 先看一个通过 protocol extention 添加默认实现的代码例子

// 定义一个人的属性的 Protocol

protocol PersonProperty {

    var height: Int { get } //cm
    var weight: Double { get } // kg
    
    // 判断体重是否合格的函数
    func isStandard() -> Bool
}

extension PersonProperty {

    // 给 protocol 添加默认的实现
    func isStandard() -> Bool {
        
        return self.weight == Double(height - 100) * 0.9
    }
    
    // 给protocol 添加默认属性
    var isPerfrctHeight: Bool {
    
        return self.height == 178
    }
}

struct Person6: PersonProperty {
    var height: Int
    var weight: Double
    // 如果自定义类型里面创建了遵守的 protocol 中的方法
    // 那么他将覆盖 protocol 中的方法
    //    func isStandard() -> Bool {
    //        return true
    //    }
}

let p1 = Person6(height: 178, weight: 61.5)
p1.isStandard()
p1.isPerfrctHeight

protocol SportsFactors {

    var sportQuantity: Double { get }
}

// 下面这种写法就用到了 extension 中的 type constraints
// 意思是 只有同时遵守了 SportsFactors 和 PersonProperty 时
// 才使 PersonProperty 获得扩展 并提供带有 sportQuantity 属性的 isStandard 方法
extension PersonProperty where Self: SportsFactors {

    func isStandard() -> Bool {
        
        // 随意写的算法 不要在意
        return self.weight == Double((height - 100)) * 0.9 - self.sportQuantity
    }
}

// 创建一个协议只能被类遵守
protocol MyClassDelegate: class {

    func getName()
}






